feat: add preserveScrollOnWrite option#181
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6703da75f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
6703da7 to
31b6df6
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31b6df6bf5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
31b6df6 to
bdf8492
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bdf84923d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bdf8492 to
bf992cf
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bf992cfc85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bf992cf to
1720cfe
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1720cfe688
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1720cfe to
343cbf6
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 343cbf6496
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
343cbf6 to
74ef4cd
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74ef4cd63c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
74ef4cd to
8b6b061
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b6b06164a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
8b6b061 to
fb0632b
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb0632b470
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
fb0632b to
49ae976
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49ae976569
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
49ae976 to
4237268
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd355e1f79
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Change-Id: I3460285d7e278bbd0a3a02a056738ae734c8eb1a Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: If0412a81b48ea28fa5779919578c6c1744e5adad Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Ifbd2f32e8e35430746dbe7859c74aec48e00ec23 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I58ddd0c9825b6d0c90ddfd965e35e2a4a9a1814b Signed-off-by: Thomas Kosiewski <tk@coder.com>
c34bc89 to
8fa1331
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8fa1331fae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I8e012a4d13ca25dff713c89f72abd443aa3e4c1f Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fef42074fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I9015dc3480366cab0dafa24f53faa243ac74d857 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e851df4e88
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I4d4f723896004bfe89539130982cc7e6951c116c Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9150a26da5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I933d583055761c436ca94a8aa941a2daa6095a39 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05a09cefd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I84165d47e7aca2452b33dfb15df5771664310630 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7668001752
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Change-Id: I6beab0dae7d2812928fab8c08a190400e81a30c9 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
1 similar comment
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #127
Summary
preserveScrollOnWrite?: booleantoITerminalOptions, defaulting tofalsefor backward-compatible snap-to-bottom behavior.targetViewportY,onScroll, and scrollbar visibility in sync.Validation
bun test lib/scrolling.test.tsbun run fmtbun run lintbun run typecheckbun testbun run buildDogfooding
Using the Vite dev server (
bun run dev -- --host 127.0.0.1) andagent-browser, I verified:viewportYfrom8to0and snapped the visible top line fromHistorical line 24to the live-bottom region.viewportYfrom8to14while preserving the same visible top line:Historical line 24..webmrecording were captured in the worker for review evidence.Verifier notes
ghostty-vt.wasm; this worker built it via./scripts/build-wasm.shbefore validation.📋 Implementation Plan
Implementation Plan: #127 —
preserveScrollOnWriteGoal
Add a minimal, opt-in terminal option that preserves the user’s scrolled-up viewport during streaming writes, while keeping the current “snap back to bottom on write” behavior as the default.
Target behavior:
preserveScrollOnWriteomitted orfalse: current behavior remains — ifTerminal.write()is called whileviewportY !== 0, the terminal scrolls back to the bottom.preserveScrollOnWrite: true: if the user is scrolled up whenTerminal.write()adds new scrollback lines, adjust the viewport offset by the scrollback growth so the same historical text remains visible instead of snapping to the bottom.Advisor review status
onScrollcoverage.Verified repo context
From focused read-only exploration:
lib/interfaces.tsITerminalOptionsis the public options interface that should receive the new optional boolean option.lib/terminal.tsTerminalconstructor builds a defaultedbaseOptionsobject fromITerminalOptionsvalues.writeInternal(data, callback)synchronously writes to the Ghostty WASM terminal, processes terminal responses, invalidates link cache, and currently callsscrollToBottom()wheneverviewportY !== 0.this.viewportY: current viewport offset from bottom;0means live bottom, values> 0mean scrolled into history.this.targetViewportY: smooth-scroll target that should stay in sync when manually changingviewportY.this.getScrollbackLength(): current WASM scrollback length.this.scrollEmitter.fire(Math.floor(this.viewportY)): existing scroll event mechanism.this.showScrollbar()/ existing scrollbar helpers: should be used consistently with current scroll paths when the preserved viewport changes.this.scrollToBottom(): existing default snap-to-bottom behavior.lib/scrolling.test.tscreateIsolatedTerminal()fromlib/test-helpers.ts, create a DOM container inbeforeEach, open the terminal, and dispose/remove it inafterEach.terminal.write(),terminal.scrollLines(),terminal.viewportY, andterminal.getScrollbackLength().demo/scrollbar-test.htmlis the most direct browser surface for scroll behavior.Scope
In scope
preserveScrollOnWrite?: booleanto the terminal options API.falsefor backward compatibility.Terminal.write()/writeInternal()scroll handling so the opt-in mode preserves scrolled-up content across writes.Out of scope
needs-triageissue with evidence, then return to Could the automatic scrollToBttom() behaviour made to be configurable/togglable? #127.Implementation phases
Phase 1 — Add the public option
Files:
lib/interfaces.tslib/terminal.tsSteps:
Add the optional property to
ITerminalOptions:In the
Terminalconstructor’s default/base options object, add:Keep naming exactly
preserveScrollOnWriteto match the issue, triage recommendation, and external fork precedent.Do not add aliases or additional configuration knobs.
Quality gate after Phase 1:
bun run typecheckif feasible at this point, or defer until tests are added if implementation is still mid-edit.Phase 2 — Preserve scrolled-up viewport during writes
File:
lib/terminal.tsRecommended algorithm in
writeInternal:After
this.assertOpen()and beforethis.wasmTerm!.write(data), capture:Leave the existing write pipeline intact:
write(data)remains synchronous.Replace only the unconditional post-write auto-scroll block with scoped conditional behavior:
Use the signed scrollback delta from the before/after measurements, then clamp. Do not mask negative deltas with
Math.max(0, delta): unusual write data may clear or reduce scrollback, and the signed-delta formula keepsviewportYwithin valid bounds while matching the issue brief.Keep the implementation minimal. Include a local defensive assertion/comment only if it documents a repository-confirmed invariant; do not turn escape-sequence behavior such as scrollback clearing into unrelated user-facing error handling.
Keep
targetViewportYsynchronized withviewportY; otherwise the existing smooth-scroll animation can pull the viewport back toward a stale target.Clamp to the new scrollback length so the viewport never points beyond available history, especially when the configured scrollback limit has been reached.
If
preserveScrollOnWriteis true but the user is at the bottom (savedViewportY === 0), do not adjust upward. The live bottom should continue showing new output. Theelse if (this.viewportY !== 0)fallback still preserves default snap-to-bottom behavior if processing somehow leaves the viewport nonzero outside the opt-in scrolled-up path.Quality gate after Phase 2:
bun test lib/scrolling.test.ts.Rationale for the scrollback-delta algorithm
viewportYis an offset from the live bottom. When the user is scrolled up and a new line enters scrollback below them, the same historical content is now one line farther away from the bottom. AdjustingviewportYby the signed scrollback delta keeps the visible top line stable for the normal streaming-output case. If no scrollback lines are added, the viewport should not move. If scrollback is reduced or old lines are dropped, signed delta plus clamping keeps offsets valid; content no longer retained by WASM cannot be preserved.Phase 3 — Add tests
File:
lib/scrolling.test.tsAdd a focused suite such as
describe('preserveScrollOnWrite', () => { ... })using the existingcreateIsolatedTerminal()pattern. For deterministic assertions, prefer settingsmoothScrollDuration: 0directly in these new tests if the option works with the existing helper; otherwise wait for the smooth-scroll animation to settle before assertingviewportY/ emitted scroll values.Required tests:
Default behavior remains snap-to-bottom
preserveScrollOnWrite.scrollLines(-N)and assertviewportY === N.viewportY === 0.Opt-in mode preserves viewport across scrollback growth and emits
onScroll{ preserveScrollOnWrite: true }.terminal.onScroll((value) => ...)before the preserving write.Nlines and recordviewportYandgetScrollbackLength().scrollLines()call so the assertion targets the preserving write.viewportY === oldViewportY + (newScrollbackLength - oldScrollbackLength), clamped to the new scrollback length.onScrollemission for the preserving write matchesMath.floor(terminal.viewportY).Opt-in mode does not move viewport when write creates no scrollback growth
{ preserveScrollOnWrite: true }.viewportYremains unchanged.Runtime option toggle works
terminal.options.preserveScrollOnWrite = true.Test hygiene:
afterEachor localtry/finally.Quality gate after Phase 3:
bun test lib/scrolling.test.tsPhase 4 — Full validation
Run the repository’s normal gates before claiming the implementation is done:
bun run fmt bun run lint bun run typecheck bun test bun run buildIf
bun testhangs after printing successful results, record the printed pass/fail summary and terminate only after confirming the test suite completed. If validation fails for an unrelated pre-existing reason, capture the exact command, failure, and why it is out of scope.Phase 5 — Dogfooding and reviewable evidence
Goal: demonstrate the behavior in a real browser terminal surface and produce evidence reviewers can inspect.
Setup:
Install dependencies if needed:
Start Vite, not a plain static file server:
Use browser automation with the
agent-browserskill or equivalent Playwright flow to open a same-origin page, preferably:Dogfood procedure:
Default mode evidence
term.write('background line\r\n')in the page context or using an existing demo control.Opt-in mode evidence
term.options.preserveScrollOnWrite = true, or instantiate a second terminal with{ preserveScrollOnWrite: true }from/lib/terminal.tsin the browser page.Evidence handling
/tmp/ghostty-web-127-dogfood/.Suggested evidence checklist:
default-before-write.png: scrolled-up viewport before a default write.default-after-write.png: viewport snapped to live bottom after default write.preserve-before-writes.png: scrolled-up viewport with opt-in enabled.preserve-after-writes.png: same visible content after several background writes.preserve-scroll-on-write.webmor equivalent short recording demonstrating repeated writes.Acceptance criteria
ITerminalOptionsincludespreserveScrollOnWrite?: boolean.TerminaldefaultspreserveScrollOnWritetofalse.preserveScrollOnWrite: true, writing while scrolled up preserves the visible historical content by adjustingviewportYby scrollback growth and clamping to available scrollback.targetViewportYsynchronized so smooth scrolling does not undo the adjustment.onScrollsubscribers are notified when preservation changes the viewport, and scrollbar visibility/position is updated consistently with existing scroll behavior.onScrollemission during preservation, no-scrollback-growth writes, and runtime toggling throughterminal.options.Risks and mitigations
targetViewportYalongsideviewportY.getScrollbackLength()before/after writes rather than assuming every write produces exactly one scrollback line.term, instantiate a temporary terminal in page context viaawait import('/lib/terminal.ts')rather than committing demo-only controls.Generated with
mux• Model:openai:gpt-5.5• Thinking:xhigh